package com.cx.config;

import com.cx.property.JedisProperties;
import com.cx.serializer.ProtoStuffRedisSerializer;
import com.cx.serializer.StringRedisSerializer;
import com.cx.utils.Const;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import redis.clients.jedis.*;
import redis.clients.util.Pool;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * @Author: 舒建辉
 * @Description:
 * @Date: Created on 2019/3/13
 * @Version: 1.0
 */
@Configuration
@EnableTransactionManagement
//@EnableConfigurationProperties({JedisProperties.class})
public class RedisTxConfiguration extends CachingConfigurerSupport {

    @Autowired
    private JedisProperties jedisProperties;

    @Primary
    @Bean
    public JedisProperties jedisProperties(){
        return new JedisProperties();
    }

    @Bean
    public JedisPoolConfig jedisPoolConfig(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(jedisProperties.getPool().getMaxActive());
        config.setMaxIdle(jedisProperties.getPool().getMaxIdle());
        config.setMinIdle(jedisProperties.getPool().getMinIdle());
        config.setMaxWaitMillis(jedisProperties.getPool().getMaxWait());
        config.setTestOnBorrow(jedisProperties.getPool().isTestOnBorrow());
        config.setTestOnReturn(jedisProperties.getPool().isTestOnReturn());
        config.setTestWhileIdle(jedisProperties.getPool().isTestWhileIdle());
        config.setTimeBetweenEvictionRunsMillis(jedisProperties.getPool().getTimeBetweenEvictionRunsMillis());
        config.setNumTestsPerEvictionRun(jedisProperties.getPool().getNumTestsPerEvictionRun());
        config.setMinEvictableIdleTimeMillis(jedisProperties.getPool().getMinEvictableIdleTimeMillis());
        return config;
    }

    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory factory = null;
        if(null != jedisProperties.getPool()) {
            factory = new JedisConnectionFactory(jedisPoolConfig());
        }

        //哨兵模式：Redis Sentinel监听主服务，再主服务发生故障时能够切换至从服务，将从服务升为主服务来保证故障恢复，使用该功能需要在JedisConnectionFactory设置RedisSentinelConfiguration属性，目前Jedis对Redis Sentinel提供支持。
        if (null != jedisProperties.getSentinel()) {
            RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration(
                    jedisProperties.getSentinel().getMaster(),
                    new HashSet<>(Arrays.asList(jedisProperties.getSentinel().getNodes().split(",")))
            );

            factory = new JedisConnectionFactory(sentinelConfig, jedisPoolConfig());
        }

        //集群模式: 一般6台做分布式集群，比较稳定
        if (null != jedisProperties.getCluster()) {
            RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(jedisProperties.getCluster().getNodes());
            redisClusterConfiguration.setMaxRedirects(jedisProperties.getCluster().getMaxRedirects());
            factory = new JedisConnectionFactory(redisClusterConfiguration, jedisPoolConfig());
        }

        //分片模式: 一般6台做分布式分片，比较稳定，其实只要多数节点配置上就可以，其它的自动发现
        if (null != jedisProperties.getShard()) {
            factory = new JedisConnectionFactory(jedisProperties.getShardInfo());
        }

        factory.setClientName(jedisProperties.getProName());
        factory.setDatabase(jedisProperties.getDatabase());
        factory.setHostName(jedisProperties.getHost());
        factory.setPassword(jedisProperties.getPassword());
        factory.setPort(jedisProperties.getPort());
        factory.setTimeout(jedisProperties.getTimeout());
        factory.afterPropertiesSet();
        return factory;
    }

    @Bean
    public Pool<Jedis> pool() {
        /**
         * 连接池模式
         */
        if(null != jedisProperties.getPool() || null != jedisProperties.getCluster()) {
            return new JedisPool(jedisPoolConfig(), jedisProperties.getHost(), jedisProperties.getPort(),
                    jedisProperties.getTimeout(), jedisProperties.getPassword(), jedisProperties.getDatabase());
        }

        /**
         * 哨兵模式
         */
        if(null != jedisProperties.getSentinel()){
            return new JedisSentinelPool(jedisProperties.getSentinel().getMaster(),
                    new HashSet<>(Arrays.asList(jedisProperties.getSentinel().getNodes().split(","))),
                    jedisPoolConfig(), jedisProperties.getTimeout(), jedisProperties.getPassword(), jedisProperties.getDatabase());
        }

        return null;
    }

    /**
     * 集群模式
     */
    @Bean
    public JedisCluster jedisCluster(){
        if(null == jedisProperties.getCluster()){
            return null;
        }

        // 添加集群的服务节点Set集合
        Set<HostAndPort> hostAndPortsSet = new HashSet<>();
        // 添加节点
        jedisProperties.getCluster().getNodes().stream().filter(p->null!=p).forEach(node -> {
            String[] hostAndPorts = node.split(Const.SP);
            hostAndPortsSet.add(new HostAndPort(hostAndPorts[0], Integer.valueOf(hostAndPorts[1])));
        });

        return new JedisCluster(hostAndPortsSet, jedisProperties.getTimeout(), jedisProperties.getTimeout(),
                jedisProperties.getCluster().getMaxRedirects(), jedisProperties.getPassword(), jedisPoolConfig());
    }

    /**
     * 分片模式连接池
     */
    public Pool<ShardedJedis> shardedJedisPool() {
        if (null == jedisProperties.getShard()) {
            return null;
        }

        return new ShardedJedisPool(jedisPoolConfig(), jedisProperties.shards());
    }

    /**
     * @Author: 舒建辉
     * @Description: 使用Repository方式
     * @Date: Created on 2018/2/7
     * @Version: 1.0
     */
    @Bean
    public RedisTemplate<String, ?> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, ?> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        // 使用kryoRedisSerializer 替换默认序列化
        //KryoRedisSerializer<Object> kryoRedisSerializer = new KryoRedisSerializer<Object>(Object.class);
        //Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        //GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
//        ObjectMapper om = new ObjectMapper();
//        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 设置value的序列化规则和 key的序列化规则
        RedisSerializer<Object> stringRedisSerializer = new StringRedisSerializer();
        ProtoStuffRedisSerializer<Object> protoStuffRedisSerializer = new ProtoStuffRedisSerializer<>(Object.class);
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(protoStuffRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(stringRedisSerializer);

        // explicitly enable transaction support
        //redisTemplate.setEnableTransactionSupport(true);
        RedisConnectionUtils.bindConnection(factory, true);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager(RedisTemplate<String, ?> redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setUsePrefix(true);
//        cacheManager.setCacheNames(Arrays.asList(REDIS_KEYSPACE_PRE+"user",REDIS_KEYSPACE_PRE+"role"));
        //设置默认的过期时间，单位秒
        cacheManager.setDefaultExpiration(jedisProperties.getCacheLevel2Ttl());

        // 还可以使用下面的方法为指定的key设定过期时间，它将会在computeExpiration方法中用到
//        Map<String, Long> expires = new HashMap<>();
//        expires.put("cacheNameKey", 20L);
//        expires.put("myKey", 40L);
//        cacheManager.setExpires(expires);
        return cacheManager;
    }

    @Bean(name="hikariDataSource")
    @Qualifier("hikariDataSource")
    @ConfigurationProperties(prefix="spring.datasource.hikari")
    public DataSource hikariDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean
    public PlatformTransactionManager transactionManager() throws SQLException{
        return new DataSourceTransactionManager(hikariDataSource());
    }


    /**
     * 配置缓存键值的生成器 (因为Redis是键值对存储的数据库, 所以要配置一个键值缓存的生成器)
     *
     * @return
     */
//    @Bean
//    public KeyGenerator keyGenerator() {
//        return new KeyGenerator() {
//            // 配置生成器
//            @Override
//            public Object generate(Object target, Method method, Object... objects) {
//                StringBuilder sb = new StringBuilder(jedisProperties.cacheLevel2Pre());
//                // 规则1: 拼接class名称
//                sb.append(target.getClass().getName());
//                sb.append(Const.SP);
//                // 规则2: 拼接方法名称
//                sb.append(method.getName());
//                sb.append(Const.SP);
//                Arrays.stream(objects).forEach(obj->{
//                    // 规则3: 拼接参数值
//                    sb.append(obj.toString());
//                    sb.append(Const.SP);
//                });
//
//                return sb.toString();
//            }
//        };
//    }
}